//TS: 强类型，编译纠错，不可改变变量数据类型
//JS: 弱类型，运行时报错，变量可被赋予不同类型值
// TS缺点： ①不能被浏览器理解，需要被编译成js ②需要时间理解



/* // 基础类型
// boolean
let isHandsome: boolean = true;
isHandsome = false
console.log(isHandsome);
// number
const age: number = 18
console.log("age",age);
// string
const realName: string = "line"
const fullName: string = `A ${realName}` //支持模版字符串
console.log("fullName",fullName);
// undefined和null类型
const u: undefined = undefined
const m: null = null
console.log("u",u);
console.log("m",m); */



/* // any unknown 和 void 类型
//any --- 不清楚用什么类型
// 场景： 来自于动态的内容，例如用户输入，第三方代码库
let notSure: any = 4 
notSure = "hello world"
notSure = false
//notSure.name  
//notSure.getName 
// 可以随便调用属性和方法
console.log(notSure);
// 不建议使用any---否则TS就失去了意义

// unknown类型
//不建议使用 any，当我不知道一个类型具体是什么时，可以使用 `unknown` 类型
// 它的定义和 `any` 定义很像，但是它是一个安全类型，使用 `unknown` 做任何事情都是不合法的。
function divide(param: unknown ) {
    return param as number/2
}
const a: number = divide(1)
console.log(a);

//void --- `void`类型与 `any` 类型相反,它表示没有任何类型
// 场景： 比如函数没有明确返回值，默认返回void类型
function aa(a: number,b:number) {
    return a+b
}
console.log(aa(1,2));

function welcome(): void{
    console.log("hello");
    
}
welcome() */



//never类型 -- 表示那些永不存在的值的类型
//场景： 1. 如果一个函数执行时抛出了异常，这个函数永远不存在返回值，因为抛出异常会直接中断程序运行。 2. 函数中执行无限循环的代码，使得程序永远无法运行到函数返回值那一步。
//never 类型是任何类型的子类型，也可以赋值给任何类型。\
//没有类型是 never 的子类型，没有类型可以赋值给 never 类型（除了 never 本身之外）。即使 `any`也不可以赋值给 never 。

//异常
/* function fn(msg: string): never {
    throw new Error(msg)
} */
// 死循环 千万别这么写，会内存溢出
/* function fn(): never {
    while (true) {}
} */

/* let test1: never
test1 = "lin" */

/* let test2: never
test1 = test2 */

// 数组类型
/* const list: number[] = [1,2,3]
// 定义了数组中的每一项为数值，否者会报错
list.push(4)
console.log("list",list); */

// 元组类型
//元组类型允许表示一个已知元素数量和类型的数组，各元素的类型不必相同。
/* const tuple: [number,string] = [18,"lin"]
// 元数组可以使用数组的方法，比如push，不会有越界报错
tuple.push(100)  //只能push定义的number或者string类型
console.log(tuple); */

// 函数类型
//TS 定义函数类型需要定义输入参数类型和输出类型。
//输出类型也可以忽略，因为 TS 能够根据返回语句自动推断出返回值类型。
/* function add(x: number,y: number): number {
    return x+y
}
console.log(add(1,2)); */

// 函数没有明确返回值，默认返回 Void 类型
/* function welcome(): void {
    console.log("hello");
}
welcome() */


/* // 函数表达式写法
const add2 = (x:number,y:number): number => {
    return x+y
}
console.log(add2(1,3));
// 可选参数 -- 只可以放在函数传入参数最后面
function add(x:number,y:number,z?:number):number {
    return x+y+z
}
console.log(add(1,2,3));
// 默认参数
function add1(x:number,y:number = 100,z:number): number {
    return x+y+z
}
console.log(add1(1,undefined,3)); // 需要注意： 如果带默认值的参数不是最后一个参数，用户必须明确的传入 undefined值来获取默认值 */


// 函数赋值--- 在TS中，函数不能随便赋值，会报错
/* const add3 = (x: number,y: number): number => {
    return x+y
}
console.log(add3(1,2));
const add4 = (x:number,y:number) => number = add2
console.log(add4); */

//interface(接口) -- 是ts设计出来用于定义对象类型的，可以对对象的形状进行描述
/* interface Person {
    name: string
    age: number
}
const p1: Person = {
    name: "张三",
    age: 13,
} */
// 属性必须和类型定义的时候完全一致 少写了属性，多写了属性，报错
//注意：interface 不是 JS 中的关键字，所以 TS 编译成 JS 之后，这些 interface 是不会被转换过去的，都会被删除掉，interface 只是在 TS 中用来做静态检查。

//可选属性 -- 跟函数的可选参数是类似的，在属性上加个 `?`，这个属性就是可选的，比如下面的 age 属性
/* interface Person {
    name: string
    age?: number
}
const p1: Person = {
   name: "lin",
   age: 18
}
console.log(p1); */

// 自定义属性 
// 对象
/* interface RandomKey {
    [propName: string]: string
}
const  obj: RandomKey ={
    a: "hello",
    b: "lin",
    c: "welcome"
} */

/* interface LikeArray {
    [propName: number]: string
}
const arr: LikeArray = ["hello","lin"]
arr[0] */
// 注意： 这种不是真的数组，数组上的方法是没有的

// duck typing （鸭子类型）
/* interface FunctionWithProps {
    (x: number): number
    fnName: string
}
const fn: FunctionWithProps = (x) => {
    return x
}
fn.fnName = "hello world"
 */

/* // 类
// 基本写法
class Person {
    name: string
    constructor(name: string) {
        this.name = name
    }
    speak() {
        console.log(`${this.name} is speaking`);
    }
}
const p1 = new Person("lin")
console.log(p1.name);
p1.speak()

// 继承
class Student extends Person {
    study() {
        console.log(`${this.name} needs study`);
    }
}
const s1 = new Student("lin")
s1.study()
s1.speak() //继承后，Student 类上的实例可以访问Person类上面的属性和方法


//super关键字
//注意，上例中 Student 类没有定义自己的属性，可以不写 super ，但是如果 Student 类有自己的属性，就要用到 super 关键字来把父类的属性继承过来。

class Student1 extends Person {
    grade: number
    constructor(name: string, grade: number){
        super(name)
        this.grade = grade
    }
}
const s2 = new Student1("lin",10)
console.log(s2);

//多态
//子类对父类的方法进行了重写，子类和父类调用同一个方法时会不一样
class Student2 extends Person {
    speak(){
        return `student ${super.speak()}`
    }
} */

//public --- 公有的，一个类里默认所有的方法和属性都是 public。
/* class Person {
    public name: string
    public constructor(name: string) {
        this.name = name
    }
    public speak(){
        console.log(`${this.name} is speaking`);
    }
} */
//public 可写可不写，不写默认也是 public。

// private 
// `private`，私有的，只属于这个类自己，它的实例和继承它的子类都访问不到。
// 将 Person 类的 name 属性改为 private。
/* class Person1 {
    private name: string
    constructor (name: string){
      this.name = name
    }
    speak() {
        console.log(`${this.name} is speaking`);
    }
}
// 实例访问name属性会报错，继承它的子类，访问name属性也会报错

// protected ---`protected` 受保护的，继承它的子类可以访问，实例不能访问。
// 将Person 类的 name 属性改为 protected。
class Person {
    protected name: string 
    public constructor(name: string) {
        this.name = name
    }
    public speak() {
        console.log(`${this.name} is speaking`);
    }
}
// 实例访问name属性会报错
// 子类可以访问
class Student extends Person {
     study() {
        console.log(`${this.name} need study`);
    }
}

//static  -- `static` 是静态属性，可以理解为是类上的一些常量，实例不能访问。
// 直接定义一个静态类
class Circle {
    static pi = 3.14
    public radius: number
    public constructor(radius: number) {
        this.radius = radius
    }
    public calcLength() {
        return Circle.pi * this.radius * 2 // 计算周长，直接访问Circle.pi
    }
} */
// 实例访问会报错

// 抽象类 --- **只能被继承，但不能被实例化的类**，
// 抽象类，听名字似乎是非常难理解的概念，但其实非常简单。
//TS 通过 `public`、`private`、`protected` 三个修饰符来增强了 JS 中的类。
// 特点： 1.抽象类不允许被实例化 2. 抽象类中的抽象方法必须被子类实现
//抽象类用一个 `abstract` 关键字来定义

//抽象类中的抽象方法必须被子类实现
/* abstract class Animal {
   constructor(name: string) {
    this.name = name 
   }
   public name: string
   public abstract sayHi(): void
}

class Dog extends Animal {
    constructor(name: string) {
        super(name)
    }
} */
//定义一个 Dog 类，继承自 Animal 类，但是却没有实现 Animal 类上的抽象方法 `sayHi`，报错，

/* abstract class Animal {
    constructor (name: string) {
        this.name = name 
    }
    public name: string 
    public abstract sayHi(): void
}

class Dog extends Animal {
    constructor(name: string) {
        super(name)
    }
    public sayHi() {
        console.log("wang");
    }
} */
//所以抽象类的用法是用来定义一个基类，声明共有属性和方法，拿去被继承。
//抽象类的好处是可以抽离出事物的共性，有利于代码的复用。

// 抽象方法和多态
// 多态是面向对象的三大基本特征之一。
// 多态指的是，父类定义一个抽象方法，在**多个子类中有不同的实现**，运行的时候不同的子类就对应不同的操作，比如，

/* abstract class Animal {
    constructor (name: string){
        this.name = name
    }
    public name: string
    public abstract sayHi(): void
}

class Dag extends Animal {
    constructor(name: string) {
        super(name)
    }
    public sayHi(){
        console.log("wang");
        
    }
}

class Cat extends Animal {
    constructor(name: string){
        super(name)
    }
    public sayHi(){
        console.log("miao");
    }
} */
//Dog 类和 Cat 类都继承自 Animal 类，Dog 类和 Cat 类都不同的实现了 sayHi 这个方法。

//interface 和 class关系
// 上文中我们说过，interface 是 TS 设计出来用于定义对象类型的，可以对对象的形状进行描述。
// interface 同样可以用来约束 class，要实现约束，需要用到 `implements` 关键字。

/* // implements
interface MusicInterface {
    playMusic(): void
}
class car implements MusicInterface {
    playMusic() {}
}
class cellphone implements MusicInterface {
    playMusic() {}
} */

// 枚举（Enum）
// 在任何项目开发中，我们都会遇到定义常量的情况，常量就是指不会被改变的值。
/* enum Direction {
   up,
   Down,
   Left,
   Right
} */
/* console.log(Direction.Up);
console.log(Direction.Down);
console.log(Direction.Left);
console.log(Direction.Right);
console.log(Direction[0]) 
console.log(Direction[1]) 
console.log(Direction[2]) 
console.log(Direction[3])  */

/* enum Direction {
   Up = 6,
   Down,
   Left,
   Right
}
console.log(Direction.Up);
console.log(Direction.Down);
console.log(Direction.Left);
console.log(Direction.Right);
 */

// 定义一个枚举来管理外卖状态，分别有已下单，配送中，已接收 三个状态
/* enum ItemStatus {
    Buy = 1,
    Send,
    Receive
}
console.log(ItemStatus['Buy'])
console.log(ItemStatus['Send'])
console.log(ItemStatus['Receive']) */

//但有时候后端给你返回的数据状态是乱的，就需要我们手动赋值。
//比如后端说 Buy 是 100，Send 是 20，Receive 是 1，就可以这么写，
/* enum ItemStatus {
    Buy = 100,
    Send = 20,
    Receive = 1
}
console.log(ItemStatus['Buy'])
console.log(ItemStatus['Send'])
console.log(ItemStatus['Receive']) */

// 计算成员
// 枚举中的成员可以被计算，比如经典的使用位运算合并权限，可以这么写，

/* enum FileAccess {
    Read = 1 << 1,
    Write = 1<< 2,
    ReadWrite = Read | Write
}
console.log(FileAccess.Read)
console.log(FileAccess.Write)
console.log(FileAccess.ReadWrite)
 */

//类型推论， 定义变量 形参 ，返回值 不标注类型， 自动根据 初始值 来推论出类型 
// 变量声明 根据 初始值 类型 推论出 变量类型
/* let a = 10
a = "哈哈"
console.log(a); */

// 变量定义只声明未赋值 会推论成any
/* let a  
a = 10
a = "你好事件"
console.log(a); */

// 函数形参 根据 形参默认值 做类型推论 函数返回值类型推论
/* function fn(x:number,b:number,c=10) {
    console.log(x,b,c);
}
fn(1,2,3) */
// 如果c传入的不是和默认值一样的类型，ts会报错

/* // 数组
const arr: number[] = [1,2,3,4]
arr.push("你好") // 如果传入的不是number类型的值，就会报错
// 使用 泛型 定义 数组
const arr2: Array<number> = [1,2,3,4]
arr.push("你好")// 如果传入的不是number类型的值，就会报错 */

// 元组 理解为特殊的数组 每个位置 可以存储不同类型的值
/* const tuple: [number,string] = [18,"lin"]
tuple.push(10) */

/* const div: HTMLDivElement = document.querySelector("div")
console.log(div); */

// 联合类型 
/* 
  类型保护 
  当我们一个值 类型为 联合类型， 不管使用 联合类型中 任何类型 方法 属性都会报错 叫做类型保护
  类型断言
  怎么理解： 相当于 告诉ts 我确定是这个类型 我使用这个类型api 你别提醒了
  使用时注意： 存在风险 （值还是你自己传的），使用的时候建议做一个判断 在使用断言的api
*/

/* function fn(a:number|string) {
    // console.log(a);
    // console.log(a.toFixed(2));
    // console.log(a.length);
    // 断言的语法 值as 类型
    // 先断言一种类型
    const aa = a as string
    // 判断这时候是否有值，就是在判断是不是这个类型
    if(aa.length) {
        console.log(aa.length);
    } else {
        //如果不是，就会走到这里 ，再重新进行一个断言
        const bb = a as number
        console.log(bb.toFixed(2));
    }
}
fn("1.222") */

// type类型别名
/* type User = {name: string,age: number}
const p1: User = {
    name: "lin",
    age: 18
} */

// 泛型 
/* 
  函数 形参类型 和 返回值类型一样 （类型还不确定）
*/
/* function fn<T>(n:T):T {
    return n 
}
fn<number>(100)

const arr: Array<number> = [1,2,3,4] 
arr.push(5) */

/* 
   关于TS的导入和导出

   导入
   这个是在导入定义的属性的规则
    import type {Person1 } from "./xxx"
    使用
    const p1: Person1  = {
        name: "小明",
        age: 18
    }
   导出
   interface Person1 {
    name: string,
    age: number
   }
   type  Person2 = {
       name: string,
       age: number,
   }
   export type {
       Person1,
       Person2
   }
    需要注意导入导出都要加type关键字，告诉他是传过去和接收的是属性不是值
*/
